﻿using MiniFox.Components;
using MiniFox.Infrastructure;
using MiniFox.Platform.Exceptions;
using MiniFox.Platform.Models;
using MiniFox.Platform.OAuth2.Configurations;
using MiniFox.Platform.OAuth2.Events;
using MiniFox.Platform.OAuth2.Models;
using MiniFox.Platform.OAuth2.Providers;
using MiniFox.Security.Text;
using MiniFox.Utility;
using MiniFox.Web;

namespace MiniFox.Platform.OAuth2.Services
{
    /// <summary>
    /// 通行证服务
    /// </summary>
    [AutoResolve]
    public class PassportService : OAuth2ServiceBase, IPassportService
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="authStateManagement"></param>
        public PassportService(IAuthenticationStateManagement authStateManagement) : base(authStateManagement)
        {
            this.StateManagement.CookieStateExpires = this.Configuration.CookieStateExpires;
        }
        /// <summary>
        /// 
        /// </summary>
        [AutoWired]
        protected IPassportProvider PassportProvider
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        [AutoWired]
        public ICipherProvider CipherProvider
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        public PassportConfiguration Configuration
        {
            get
            {
                return this.ConfigProvider.PassportConfiguration;
            }
        }

        #region 事件

        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<RegisterEventArgs> RegisterEvent;
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<SignInEventArgs> SignInEvent;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected void OnRegistered(RegisterEventArgs e)
        {
            this.RegisterEvent?.Invoke(this, e);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnSignIn(SignInEventArgs e)
        {
            this.SignInEvent?.Invoke(this, e);
        }
        #endregion

        #region 登录
        /// <summary>
        /// 账密登录
        /// </summary>
        /// <param name="authTicket">待授权票据</param>
        /// <param name="passwordSignIn">账密包</param>
        /// <returns>是否登录成功</returns>
        public bool SignIn(Ticket authTicket, PasswordSignIn passwordSignIn)
        {
            if (authTicket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }

            Cipher password = this.CipherProvider.CreateCipher(passwordSignIn.Password);
            var identity = this.PassportProvider.GetIdentity(passwordSignIn.SignName, password);
            var success = identity != null;
            if (success)
            {
                this.CreatePassportToken(authTicket, identity);
            }
            return success;
        }
        /// <summary>
        /// 使用注册邮箱和验证码登录 
        /// </summary>
        /// <param name="authTicket">待授权票据</param>
        /// <param name="pinCodeSignIn">验证码包</param>
        /// <returns>是否登录成功</returns>
        public bool SignIn(Ticket authTicket, PinCodeSignIn pinCodeSignIn)
        {
            if (authTicket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }
            //验码,失败时自动异常抛出
            this.CheckPinCode(authTicket, pinCodeSignIn);

            var identity = this.PassportProvider.GetIdentityByCellphone(pinCodeSignIn.Cellphone);
            var success = identity != null;
            if (success)
            {
                this.CreatePassportToken(authTicket, identity);
            }
            else
            {
                string signName = pinCodeSignIn.Cellphone;
                string password = Tools.GenerateRandomCode(8);
                success = Configuration.AutoRegister && Register(authTicket, signName, password);
            }
            return success;
        }

        #region 通过设备app（微信、钉钉、支付宝）登录支持
        /// <summary>
        /// PC端轮询接口，检查PC票据是否已在移动终端被确认授权
        /// </summary>
        /// <param name="authTicket">待授权票据</param>
        /// <returns></returns>
        public virtual AuthorizeResult SignIn(Ticket authTicket)
        {
            if (authTicket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }
            AuthorizeResult result = this.PassportProvider.GetDeviceAuthorize(authTicket);
            if (result.Status == DeviceAuthorizeStatus.Authorized) //已授权
            {
                var identity = this.PassportProvider.GetIdentity(result.OpenAuthUser) ?? throw new UnregisteredException(result.OpenAuthUser);
                CreatePassportToken(authTicket, identity);
            }
            return result;
        }

        #endregion

        #region 移动终端操作项

        /// <summary>
        /// 在设备上使用第三方APP(如:微信、钉钉)授权登录，未关联到本系统账户将引发UnregisteredException异常
        /// </summary>
        /// <param name="authTicket">待授权票据</param>
        /// <param name="openAuthUser">第三方用户信息</param>
        /// <returns></returns>
        public bool SignIn(Ticket authTicket, ExternalOpenAuthUser openAuthUser)
        {
            return SignIn(authTicket, openAuthUser, false);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="authTicket"></param>
        /// <param name="openAuthUser"></param>
        /// <param name="scanned"></param>
        /// <returns></returns>
        public bool SignIn(Ticket authTicket, ExternalOpenAuthUser openAuthUser, bool scanned)
        {
            if (authTicket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }
            if (scanned)
            {
                return this.PassportProvider.SetOpenAuthUser(authTicket, openAuthUser);
            }
            else
            {
                var key = $"identity-{openAuthUser.AuthName}-{openAuthUser.OpenID}";
                var identity = Cache.Get(key, () =>
                {
                    this.PassportProvider.SaveOpenAuthUser(openAuthUser);
                    return this.PassportProvider.GetIdentity(openAuthUser);
                });
                var success = identity != null;
                if (success)
                {
                    this.CreatePassportToken(authTicket, identity);
                }
                else
                {
                    this.PassportProvider.SetOpenAuthUser(authTicket, openAuthUser);
                    throw new UnregisteredException(openAuthUser);
                }
                return success;
            }
        }
        /// <summary>
        /// 移动终端扫码后，在移动终端对PC票据确认授权
        /// </summary>
        /// <param name="authTicket">PC端关联的登录票据</param>
        public virtual void DeviceAuthorized(Ticket authTicket)
        {
            if (authTicket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }
            this.PassportProvider.SetDeviceAuthorized(authTicket);
        }

        #endregion


        #endregion

        #region 注册
        /// <summary>
        /// 创建身份信息(标准注册流程)
        /// </summary>
        /// <param name="authTicket"></param>
        /// <param name="register"></param>
        /// <returns></returns>
        public bool Register(Ticket authTicket, Register register)
        {
            if (!this.Configuration.EnableRegister)
            {
                throw new PlatformException("ERR_DisableRegAccount");
            }
            if (authTicket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }

            if (!this.PassportProvider.IsUnusedLoginName(register.LoginName))
            {
                throw new PlatformException("ERR_LoginNameExists");
            }
            else if (!this.PassportProvider.IsUnusedSecEmail(register.SecEmail))
            {
                throw new PlatformException("ERR_SecmailExists");
            }
            else if (!string.IsNullOrEmpty(register.Cellphone) && !this.PassportProvider.IsUnusedCellphone(register.Cellphone))
            {
                throw new PlatformException("ERR_CellphoneExists");
            }
            var password = this.CipherProvider.CreateCipher(register.Password);
            var identity = this.PassportProvider.CreateIdentity(register.LoginName, register.SecEmail, register.Cellphone, password);
            bool success = identity != null;
            if (success)
            {
                this.SendActivateEmail(identity);
                this.CreatePassportToken(authTicket, identity);
                this.OnRegistered(new RegisterEventArgs(identity));
            }
            return success;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="authTicket"></param>
        /// <param name="pinCodeSignIn"></param>
        /// <returns></returns>
        public virtual bool Register(Ticket authTicket, PinCodeSignIn pinCodeSignIn)
        {
            if (!this.Configuration.EnableRegister)
            {
                throw new PlatformException("ERR_DisableRegAccount");
            }

            if (authTicket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }
            this.CheckPinCode(authTicket, pinCodeSignIn);
            string signName = pinCodeSignIn.Cellphone;
            string password = Tools.GenerateRandomCode(8);
            //使用随机8位密码创建用户
            var success = this.Register(authTicket, signName, password);
            return success;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="authTicket"></param>
        /// <param name="signName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public virtual bool Register(Ticket authTicket, string signName, string password)
        {
            if (!this.Configuration.EnableRegister)
            {
                throw new PlatformException("ERR_DisableRegAccount");
            }
            password = this.CipherProvider.CreateCipher(password);
            var identity = CreateIdentity(signName, password);
            bool success = identity != null;
            if (success)
            {
                this.CreatePassportToken(authTicket, identity);
                this.OnRegistered(new RegisterEventArgs(identity));
            }
            return success;
        }

        /// <summary>
        /// 创建用户身份
        /// </summary>
        /// <param name="signName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        protected OpenAuthIdentity CreateIdentity(string signName, string password)
        {
            Cipher cipher = this.CipherProvider.CreateCipher(password);

            if (Configuration.EmailRegex.IsMatch(signName))//Email账户创建
            {
                if (!this.PassportProvider.IsUnusedSecEmail(signName))
                {
                    throw new PlatformException("ERR_SecmailExists");
                }
                var identity = this.PassportProvider.CreateIdentityWithSecEmail(signName, cipher);
                this.SendActivateEmail(identity);
                return identity;
            }
            if (Configuration.CellphoneRegex.IsMatch(signName)) //手机用户创建
            {
                if (!this.PassportProvider.IsUnusedCellphone(signName))
                {
                    throw new PlatformException("ERR_CellphoneExists");
                }
                var identity = this.PassportProvider.CreateIdentityWithCellphone(signName, cipher);
                return identity;
            }
            if (Configuration.LoginNameRegex.IsMatch(signName))//用户名创建
            {
                if (!this.PassportProvider.IsUnusedLoginName(signName))
                {
                    throw new PlatformException("ERR_LoginNameExists");
                }
                var identity = this.PassportProvider.CreateIdentityWithLoginName(signName, cipher);
                return identity;
            }

            throw new PlatformException("ERR_InvalidSignName");
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="identity"></param>
        protected virtual void SendActivateEmail(OpenAuthIdentity identity)
        {
            if (identity != null)
            {
                Ticket ticket = new(SequenceGUID.NewGUID(), Configuration.ActivateExpires);
                if (!this.TicketProvider.SetTicketData(ticket, TicketType.ActivateIdentity, identity))
                    throw new PlatformException("ERR_CreateActiveTicketFail");

                //这里发邮件
            }
        }
        /// <summary>
        /// 通过票据，在待激活记录中取匹配项所包含的身份信息
        /// </summary>
        /// <param name="ticket">票据</param>
        /// <returns>激活是否成功</returns>
        public virtual bool ActivateIdentity(Ticket ticket)
        {
            bool success = ticket.IsExpired();
            if (success)
            {
                throw new PlatformException("ERR_InvalidTicket");
            }
            var identity = this.TicketProvider.GetTicketData<OpenAuthIdentity>(ticket, TicketType.ActivateIdentity);
            if (identity != null)
                this.TicketProvider.VerifyTicket(ticket);
            this.PassportProvider.ActivateIdentity(identity);
            success &= (identity != null);
            if (success)
                CreatePassportToken(ticket, identity);
            return success;
        }

        #endregion


        #region  通行证令牌

        /// <summary>
        /// 创建通行证令牌
        /// </summary>
        /// <param name="authTicket"></param>
        /// <param name="identity"></param>
        /// <returns></returns>
        protected void CreatePassportToken(Ticket authTicket, OpenAuthIdentity identity)
        {
            bool disableMultiSignIn = Configuration.DisableMultiSignIn;
            var clientInfomation = this.StateManagement.ClientInfomation;
            if (disableMultiSignIn)
                this.PassportProvider.DisablePassportToken(identity, clientInfomation);

            var passportToken = new PassportToken(authTicket.TicketID, Configuration.PassportExpires);
            //创建登录令牌
            this.PassportProvider.SavePassportToken(identity, passportToken, clientInfomation);
            //将停牌票据存到状态管理中
            this.StateManagement.AuthTicketID = authTicket.TicketID;

            Cache.Add($"{OpenAuthKey.PASSPORT_TOKEN}-{authTicket.TicketID}", passportToken, Configuration.PassportExpires);
            //触发登录事件
            this.OnSignIn(new SignInEventArgs(identity));
        }

        /// <summary>
        /// 对外开放平台身份绑定
        /// </summary>
        /// <param name="authTicket"></param>
        /// <returns></returns>
        public void BindIdentity(Ticket authTicket)
        {
            if (authTicket.IsExpired())
                throw new PlatformException("ERR_InvalidTicket");
            AuthorizeResult result = this.PassportProvider.GetDeviceAuthorize(authTicket);
            if (result == null || result.OpenAuthUser == null)
                throw new PlatformException("MSG_InvalidTicket");
            var identity = this.PassportProvider.GetIdentity(this.PassportToken);
            this.PassportProvider.BindIdentity(result.OpenAuthUser, identity);
        }
        #endregion

        #region 跳转
        /// <summary>
        /// 根据授权票据提取跳转地址
        /// </summary>
        /// <param name="authTicket"></param>
        /// <returns></returns>
        public virtual string CreateAuthUri(Ticket authTicket)
        {
            try
            {
                var data = Cache.Get(authTicket.TicketID, () => this.TicketProvider.GetTicketData<Dictionary<string, string>>(authTicket, TicketType.Authorization));
                string redirectUri = this.Configuration.AuthorizationUri.AddQueryString<string, string>(data);
                return redirectUri;
            }
            catch
            {
                return string.Empty;
            }
        }
        #endregion

        private void CheckPinCode(Ticket authTicket, PinCodeSignIn pinCodeSignIn)
        {
            //检查验证码
            //无效则抛出异常
            if (!this.PassportProvider.PinCodeValidation(authTicket, pinCodeSignIn.Cellphone, pinCodeSignIn.PinCode))
                throw new PlatformException("ERR_InvalidPinCode");
        }


        #region 颁发重置密码的票据
        /// <summary>
        /// 为指定用户邮箱颁发重置密码的票据(找回密码使用)
        /// </summary>
        /// <param name="signName"></param>
        /// <param name="retakePasswordMode"></param>
        /// <returns></returns>
        /// <exception cref="PlatformException"></exception>
        public bool IssueTicket(string signName, RetakePasswordMode retakePasswordMode)
        {
            OpenAuthIdentity openAuthIdentity = null;
            switch (retakePasswordMode)
            {
                case RetakePasswordMode.SignName:
                    openAuthIdentity = this.PassportProvider.GetIdentity(signName);
                    break;
                case RetakePasswordMode.Email:
                default:
                    openAuthIdentity = this.PassportProvider.GetIdentityByEmail(signName);
                    break;
            }

            if (openAuthIdentity == null)
            {
                throw new PlatformException("ERR_UnmatchedUser");
            }

            //出票
            var ticket = new Ticket(SequenceGUID.NewGUID());
            return this.TicketProvider.SetTicketData(ticket, TicketType.RetakePassword, openAuthIdentity);
        }
        #endregion

        #region 密码管理
        /// <summary>
        /// 凭票据重置密码(找回密码使用)
        /// </summary>
        /// <param name="ticket">票据</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        public bool ResetPassword(Ticket ticket, string password)
        {
            if (ticket.IsExpired())
            {
                throw new PlatformException("ERR_InvalidTicket");
            }
            Cipher cipher = this.CipherProvider.CreateCipher(password);
            return this.PassportProvider.ResetPassword(ticket, cipher);
        }
        #endregion

    }
}
